/*
 * Copyright (c) 2024 Arm Limited. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * -----------------------------------------------------------------------------
 *
 * $Date:       13. November 2024
 * $Revision:   V2.1
 *
 * Project:     USB Host Driver for STMicroelectronics STM32 devices
 *
 * -----------------------------------------------------------------------------
 */

/*! \page usbh_stm32 USB Host

# Revision History

- Version 2.1
  - Added support for USB HS in FS mode (when using the internal PHY)  
- Version 2.0
  - Initial release

# Requirements

This driver requires the STM32 device specific **HAL** and **STM32CubeMX** (CubeMX) initialization code generator.
The driver instance is mapped to hardware as shown in the table below:

  CMSIS Driver Instance | STM32 Hardware Resource
  :---------------------|:-----------------------
  Driver_USBH0          | USB_OTG_FS or USB_FS or USB_DRD_FS
  Driver_USBH1          | USB_OTG_HS

# Deviations

This driver has the following deviations from the CMSIS-Driver specification:

__Conceptual__ deviations:
  - CubeMX generated initialization code (function MX_USB_...HCD_Init) already configures
    the peripheral. Power, clocks, pins, and interrupts are enabled after execution
    of initialization that executes in `main.c`.
  - Role (Host to Device) cannot be changed at run-time.

__Functional__ deviations:
  - Initialize:
    - depends on the code generated by CubeMX.
    - does not initialize the pins.
  - Uninitialize:
    - does not de-initialize the pins.
  - PowerControl:
    - depends on the code generated by CubeMX.
    - low-power mode is not supported by HAL.
  - PortSuspend/PortResume/PipeReset/PipeDelete/PipeTransferAbort:
    - not supported by HAL.
  - PortGetState:
    - over-current status is not supported by HAL.
  - SignalPortEvent:
    - over-current signaling is not supported by HAL.

# CubeMX Configuration

This driver requires the following configuration in CubeMX:

  - **clock**: **USB** peripheral clock at **48 MHz**.
  - **peripheral**:
    - for **USB** or **USB_OTG_FS** configured as **Host_Only** or **Mode = Host_Only**.
    - for **USB_OTG_HS** in **high-speed** mode: configured as **External Phy = Host_Only** if ULPI Phy is populated
      , and **Parameter Settings** configured as desired.
    - for **USB_OTG_HS** in **full-speed** mode: configured as **Internal FS Phy = Host_Only** if ULPI Phy is not populated
      , and **Parameter Settings** configured as desired.
  - **pins**:
    - for **USB** or **USB_OTG_FS** in **full-speed** mode: **USB_OTG_FS_DM**, **USB_OTG_FS_DP pins** and pin
      for VBUS control (usually GPIO or via Multi Function eXpander (MFX))
    - for **USB_OTG_HS** in **high-speed** mode: **USB_OTG_HS_ULPI_CK**, **USB_OTG_HS_ULPI_D0**, **USB_OTG_HS_ULPI_D1**
      , **USB_OTG_HS_ULPI_D2**, **USB_OTG_HS_ULPI_D3**, **USB_OTG_HS_ULPI_D4**, **USB_OTG_HS_ULPI_D5**, **USB_OTG_HS_ULPI_D6**
      , **USB_OTG_HS_ULPI_D7**, **USB_OTG_HS_ULPI_DIR**, **USB_OTG_HS_ULPI_NXT** and **USB_OTG_HS_ULPI_STP pins**.
    - for **USB_OTG_HS** in **full-speed** mode: **USB_OTG_HS_VBUS**, **USB_OTG_HS_DM** and **USB_OTG_HS_DP pins**.
  - **interrupts**:
    - for **USB** or **USB_FS** or **USB_OTG_FS** enabled **USB** or **USB FS global interrupt** or **USB On The Go FS global interrupt**
      and **IRQ handlers** that **Call HAL handlers**.
    - for **USB_OTG_HS** enabled **USB On The Go HS global interrupt** and **IRQ handlers** that **Call HAL handlers**.

## Example

### Pinout & Configuration tab

  1. In the **Pinout view** window click on a pin and select it's functionality:
       Pin      | Functionality
       :--------|:--------------------:
       PA11     | **USB_OTG_FS_DM**
       PA12     | **USB_OTG_FS_DP**
       PA5      | **USB_OTG_HS_ULPI_CK**
       PA3      | **USB_OTG_HS_ULPI_D0**
       PB0      | **USB_OTG_HS_ULPI_D1**
       PB1      | **USB_OTG_HS_ULPI_D2**
       PB10     | **USB_OTG_HS_ULPI_D3**
       PB11     | **USB_OTG_HS_ULPI_D4**
       PB12     | **USB_OTG_HS_ULPI_D5**
       PB13     | **USB_OTG_HS_ULPI_D6**
       PB5      | **USB_OTG_HS_ULPI_D7**
       PI11     | **USB_OTG_HS_ULPI_DIR**
       PH4      | **USB_OTG_HS_ULPI_NXT**
       PC0      | **USB_OTG_HS_ULPI_STP**
     \n

  2. Under **Categories**: **Connectivity** select **USB_OTG_FS**:

     __Mode__:
       - Mode: **Host_Only**
       - Activate_VBUS: **Disable**
       - Activate_SOF: unchecked

     __Configuration__:
       - Parameter Settings: as desired
       - GPIO Settings:
           Pin Name | Signal on Pin       | Pin Context..| GPIO output..| GPIO mode                     | GPIO Pull-up/Pull..| Maximum out..| Fast Mode | User Label
           :--------|:-------------------:|:------------:|:------------:|:-----------------------------:|:------------------:|:------------:|:---------:|:----------:
           PA11     | USB_OTG_FS_DM       | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PA12     | USB_OTG_FS_DP       | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
         \n

  3. Under **Categories**: **Connectivity** select **USB_OTG_HS**:

     __Mode__:
       - External Phy: **Host_Only**
       - Internal FS Phy: Disable
       - Activate_SOF: unchecked
       - Activate_VBUS: Disable

     __Configuration__:
       - Parameter Settings: as desired
       - GPIO Settings:
           Pin Name | Signal on Pin       | Pin Context..| GPIO output..| GPIO mode                     | GPIO Pull-up/Pull..| Maximum out..| Fast Mode | User Label
           :--------|:-------------------:|:------------:|:------------:|:-----------------------------:|:------------------:|:------------:|:---------:|:----------:
           PA5      | USB_OTG_HS_ULPI_CK  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PA3      | USB_OTG_HS_ULPI_D0  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB0      | USB_OTG_HS_ULPI_D1  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB1      | USB_OTG_HS_ULPI_D2  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB10     | USB_OTG_HS_ULPI_D3  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB11     | USB_OTG_HS_ULPI_D4  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB12     | USB_OTG_HS_ULPI_D5  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB13     | USB_OTG_HS_ULPI_D6  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PB5      | USB_OTG_HS_ULPI_D7  | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PI11     | USB_OTG_HS_ULPI_DIR | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PH4      | USB_OTG_HS_ULPI_NXT | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
           PC0      | USB_OTG_HS_ULPI_STP | n/a          | n/a          | Alternate Function Push Pull  | No pull-up and no..| **High**     | n/a       | .
         \n

  4. Under **Categories**: **System Core** select **NVIC**:

     __Configuration__:
       - NVIC:
           NVIC Interrupt Table              | Enabled     | Preemption Priority | Sub Priority
           :---------------------------------|:-----------:|:-------------------:|:------------:
           USB On The Go HS global interrupt | **checked** | 0                   | 0
           USB On The Go FS global interrupt | **checked** | 0                   | 0
         \n

       - Code generation:
           Enabled interrupt table           | Select for..| Generate Enable in..| Generate IRQ h.. | Call HAL handler
           :---------------------------------|:-----------:|:-------------------:|:----------------:|:----------------:
           USB On The Go HS global interrupt | unchecked   | checked             | checked          | checked
           USB On The Go FS global interrupt | unchecked   | checked             | checked          | checked
         \n

### Clock Configuration tab

  1. Configure **To USB (MHz)**: **48**

### Project Manager tab

  1. Under **Advanced Settings**:

     __Generated Function Calls__:
       Generate Code | Function Name               | Peripheral Inst..| Do not generate ..| Visibility (Static)
       :-------------|:---------------------------:|:----------------:|:-----------------:|:-------------------:
       checked       | MX_USB_OTG_FS_HCD_Init      | USB_OTG_FS       | unchecked         | checked
       checked       | MX_USB_OTG_HS_HCD_Init      | USB_OTG_HS       | unchecked         | checked

## Source Code

If VBUS pin is not under control of USB Controller but is controlled with GPIO or Multi Function eXpander (MFX)
then it is necessary to provide hardware-specific function **USBH_HW_VbusOnOff** for VBUS pin control.

Example:
~~~
//  \fn          int32_t USBH_HW_VbusOnOff (HCD_HandleTypeDef *ptr_hhcd, bool vbus)
//  \brief       Hardware-specific control of VBUS line.
//  \param[in]   ptr_hhcd         Pointer to hhcd
//  \param[in]   vbus
//                - \b false VBUS off
//                - \b true  VBUS on
//  \return      0 on success, -1 on error.

int32_t USBH_HW_VbusOnOff (HCD_HandleTypeDef *ptr_hhcd, bool vbus) {
  if (ptr_hhcd == &hhcd_USB_OTG_FS) {
    if (vbus) {                         // VBUS power on
      BSP_IO_WritePin(0U, IO_PIN_7, IO_PIN_SET);
    } else {                            // VBUS power off
      BSP_IO_WritePin(0U, IO_PIN_7, IO_PIN_RESET);
    }
  }
  return 0;
}
~~~
*/

/*! \cond */

#include "USBH_STM32.h"

#include <string.h>

#include "cmsis_compiler.h"

// Driver Version **************************************************************
                                                //  CMSIS Driver API version           , Driver version
static  const ARM_DRIVER_VERSION driver_version = { ARM_DRIVER_VERSION_MAJOR_MINOR(2,4), ARM_DRIVER_VERSION_MAJOR_MINOR(2,1) };
// *****************************************************************************

// Driver Capabilities *********************************************************
static const ARM_USBH_CAPABILITIES driver_capabilities = {
  0x0001U,                      // Root HUB available Ports Mask
  0U,                           // Automatic SPLIT packet handling
  1U,                           // Signal Connect event
  1U,                           // Signal Disconnect event
  0U,                           // Signal Over-current event
  0U                            // Reserved (must be zero)
};
// *****************************************************************************

// Compile-time configuration **************************************************

// Configuration depending on MX_Device.h

// Check if at least one peripheral instance is configured in STM32CubeMX
#if    (!defined(MX_USBH0) && \
        !defined(MX_USBH1))
#error  USB Host driver requires at least one USB (Host) peripheral configured in STM32CubeMX!

// Check if MX_Device.h version is as required (old version did not have all the necessary information)
#elif  (!defined(MX_DEVICE_VERSION) || (MX_DEVICE_VERSION < 0x01000000U))
#error  USB Host driver requires new MX_Device.h configuration, please regenerate MX_Device.h file!

#else
#define DRIVER_CONFIG_VALID     1
#endif

// Configuration depending on the local macros

// Compile-time configuration (that can be externally overridden if necessary)
// Maximum number of pipes
#ifndef USBH_MAX_PIPE_NUM
#define USBH_MAX_PIPE_NUM       (16U)
#endif

// *****************************************************************************

#ifdef  DRIVER_CONFIG_VALID     // Driver code is available only if configuration is valid

// Macros
// Macro for section for RW info
#ifdef  USBH_SECTION_NAME
#define USBHn_SECTION_(name,n)  __attribute__((section(name #n)))
#define USBHn_SECTION(n)        USBHn_SECTION_(USBH_SECTION_NAME,n)
#else
#define USBHn_SECTION(n)
#endif

// Macro to create usbh_ro_info and usbh_rw_info (for instances)
#define INFO_DEFINE(n)                                                                                                 \
extern  HCD_HandleTypeDef       MX_USBH##n##_HANDLE;                                                                   \
static        RW_Info_t         usbh##n##_rw_info USBHn_SECTION(n);                                                    \
static  const RO_Info_t         usbh##n##_ro_info = { &MX_USBH##n##_HANDLE,                                            \
                                                      &usbh##n##_rw_info                                               \
                                                    };

// Macro for declaring functions (for instances)
#define FUNCS_DECLARE(n)                                                                                               \
static  int32_t                 USBH##n##_Initialize                (ARM_USBH_SignalPortEvent_t cb_port_event,         \
                                                                     ARM_USBH_SignalPipeEvent_t cb_pipe_event);        \
static  int32_t                 USBH##n##_Uninitialize              (void);                                            \
static  int32_t                 USBH##n##_PowerControl              (ARM_POWER_STATE state);                           \
static  int32_t                 USBH##n##_PortVbusOnOff             (uint8_t  port, bool vbus);                        \
static  int32_t                 USBH##n##_PortReset                 (uint8_t  port);                                   \
static  int32_t                 USBH##n##_PortSuspend               (uint8_t  port);                                   \
static  int32_t                 USBH##n##_PortResume                (uint8_t  port);                                   \
static  ARM_USBH_PORT_STATE     USBH##n##_PortGetState              (uint8_t  port);                                   \
static  ARM_USBH_PIPE_HANDLE    USBH##n##_PipeCreate                (uint8_t  dev_addr,                                \
                                                                     uint8_t  dev_speed,                               \
                                                                     uint8_t  hub_addr,                                \
                                                                     uint8_t  hub_port,                                \
                                                                     uint8_t  ep_addr,                                 \
                                                                     uint8_t  ep_type,                                 \
                                                                     uint16_t ep_max_packet_size,                      \
                                                                     uint8_t  ep_interval);                            \
static  int32_t                 USBH##n##_PipeModify                (ARM_USBH_PIPE_HANDLE pipe_hndl,                   \
                                                                     uint8_t  dev_addr,                                \
                                                                     uint8_t  dev_speed,                               \
                                                                     uint8_t  hub_addr,                                \
                                                                     uint8_t  hub_port,                                \
                                                                     uint16_t ep_max_packet_size);                     \
static  int32_t                 USBH##n##_PipeDelete                (ARM_USBH_PIPE_HANDLE pipe_hndl);                  \
static  int32_t                 USBH##n##_PipeReset                 (ARM_USBH_PIPE_HANDLE pipe_hndl);                  \
static  int32_t                 USBH##n##_PipeTransfer              (ARM_USBH_PIPE_HANDLE pipe_hndl,                   \
                                                                     uint32_t packet,                                  \
                                                                     uint8_t *data,                                    \
                                                                     uint32_t num);                                    \
static  uint32_t                USBH##n##_PipeTransferGetResult     (ARM_USBH_PIPE_HANDLE pipe_hndl);                  \
static  int32_t                 USBH##n##_PipeTransferAbort         (ARM_USBH_PIPE_HANDLE pipe_hndl);                  \
static  uint16_t                USBH##n##_GetFrameNumber            (void);

// Macro for defining functions (for instances)
#define FUNCS_DEFINE(n)                                                                                                                                                                                           \
static  int32_t                 USBH##n##_Initialize                (ARM_USBH_SignalPortEvent_t cb_port_event,                                                                                                    \
                                                                     ARM_USBH_SignalPipeEvent_t cb_pipe_event)         { return USBHn_Initialize                (&usbh##n##_ro_info, cb_port_event, cb_pipe_event); } \
static  int32_t                 USBH##n##_Uninitialize              (void)                                             { return USBHn_Uninitialize              (&usbh##n##_ro_info); }                           \
static  int32_t                 USBH##n##_PowerControl              (ARM_POWER_STATE state)                            { return USBHn_PowerControl              (&usbh##n##_ro_info, state); }                    \
static  int32_t                 USBH##n##_PortVbusOnOff             (uint8_t  port, bool vbus)                         { return USBHn_PortVbusOnOff             (&usbh##n##_ro_info, port, vbus); }               \
static  int32_t                 USBH##n##_PortReset                 (uint8_t  port)                                    { return USBHn_PortReset                 (&usbh##n##_ro_info, port); }                     \
static  int32_t                 USBH##n##_PortSuspend               (uint8_t  port)                                    { return USBHn_PortSuspend               (&usbh##n##_ro_info, port); }                     \
static  int32_t                 USBH##n##_PortResume                (uint8_t  port)                                    { return USBHn_PortResume                (&usbh##n##_ro_info, port); }                     \
static  ARM_USBH_PORT_STATE     USBH##n##_PortGetState              (uint8_t  port)                                    { return USBHn_PortGetState              (&usbh##n##_ro_info, port); }                     \
static  ARM_USBH_PIPE_HANDLE    USBH##n##_PipeCreate                (uint8_t  dev_addr,                                                                                                                           \
                                                                     uint8_t  dev_speed,                                                                                                                          \
                                                                     uint8_t  hub_addr,                                                                                                                           \
                                                                     uint8_t  hub_port,                                                                                                                           \
                                                                     uint8_t  ep_addr,                                                                                                                            \
                                                                     uint8_t  ep_type,                                                                                                                            \
                                                                     uint16_t ep_max_packet_size,                                                                                                                 \
                                                                     uint8_t  ep_interval)                             { return USBHn_PipeCreate                (&usbh##n##_ro_info, dev_addr, dev_speed, hub_addr, hub_port, ep_addr, ep_type, ep_max_packet_size, ep_interval); } \
static  int32_t                 USBH##n##_PipeModify                (ARM_USBH_PIPE_HANDLE pipe_hndl,                                                                                                              \
                                                                     uint8_t  dev_addr,                                                                                                                           \
                                                                     uint8_t  dev_speed,                                                                                                                          \
                                                                     uint8_t  hub_addr,                                                                                                                           \
                                                                     uint8_t  hub_port,                                                                                                                           \
                                                                     uint16_t ep_max_packet_size)                      { return USBHn_PipeModify                (&usbh##n##_ro_info, pipe_hndl, dev_addr, dev_speed, hub_addr, hub_port, ep_max_packet_size); } \
static  int32_t                 USBH##n##_PipeDelete                (ARM_USBH_PIPE_HANDLE pipe_hndl)                   { return USBHn_PipeDelete                (&usbh##n##_ro_info, pipe_hndl); }                \
static  int32_t                 USBH##n##_PipeReset                 (ARM_USBH_PIPE_HANDLE pipe_hndl)                   { return USBHn_PipeReset                 (&usbh##n##_ro_info, pipe_hndl); }                \
static  int32_t                 USBH##n##_PipeTransfer              (ARM_USBH_PIPE_HANDLE pipe_hndl,                                                                                                              \
                                                                     uint32_t packet,                                                                                                                             \
                                                                     uint8_t *data,                                                                                                                               \
                                                                     uint32_t num)                                     { return USBHn_PipeTransfer              (&usbh##n##_ro_info, pipe_hndl, packet, data, num); } \
static  uint32_t                USBH##n##_PipeTransferGetResult     (ARM_USBH_PIPE_HANDLE pipe_hndl)                   { return USBHn_PipeTransferGetResult     (&usbh##n##_ro_info, pipe_hndl); }                \
static  int32_t                 USBH##n##_PipeTransferAbort         (ARM_USBH_PIPE_HANDLE pipe_hndl)                   { return USBHn_PipeTransferAbort         (&usbh##n##_ro_info, pipe_hndl); }                \
static  uint16_t                USBH##n##_GetFrameNumber            (void)                                             { return USBHn_GetFrameNumber            (&usbh##n##_ro_info); }

// Macro for defining driver structures (for instances)
#define USBH_DRIVER(n)                  \
ARM_DRIVER_USBH Driver_USBH##n = {      \
  USBH_GetVersion,                      \
  USBH_GetCapabilities,                 \
  USBH##n##_Initialize,                 \
  USBH##n##_Uninitialize,               \
  USBH##n##_PowerControl,               \
  USBH##n##_PortVbusOnOff,              \
  USBH##n##_PortReset,                  \
  USBH##n##_PortSuspend,                \
  USBH##n##_PortResume,                 \
  USBH##n##_PortGetState,               \
  USBH##n##_PipeCreate,                 \
  USBH##n##_PipeModify,                 \
  USBH##n##_PipeDelete,                 \
  USBH##n##_PipeReset,                  \
  USBH##n##_PipeTransfer,               \
  USBH##n##_PipeTransferGetResult,      \
  USBH##n##_PipeTransferAbort,          \
  USBH##n##_GetFrameNumber              \
};

// Pipe related macros
#define PIPE_HANDLE_FLAG        (0x8000U)

// Driver status
typedef struct {
  uint8_t                       initialized  : 1;       // Initialized status: 0 - not initialized, 1 - initialized
  uint8_t                       powered      : 1;       // Power status:       0 - not powered,     1 - powered
  uint8_t                       reserved     : 6;       // Reserved (for padding)
} DriverStatus_t;

// Host Channel information
typedef struct {
           uint8_t              ch_used;                // Channel used
           uint8_t              periodic;               // Periodic transfers used on this channel
           uint8_t              ep_addr;                // Endpoint address
           uint8_t              ep_type;                // Endpoint type
           uint16_t             ep_max_packet_size;     // Endpoint maximum packet size
           uint16_t             interval;               // Polling interval (for Interrupt and Isochronous endpoints)
           uint16_t             cd_time;                // Countdown time value until next transfer
           uint8_t              hal_direction;          // Transfer direction
           uint8_t              hal_token;              // Transfer token
  volatile uint32_t             transfer_registered;    // Transfer registered information
           uint8_t             *data;                   // Pointer to buffer, containing data to send or where data will be received
           uint32_t             num;                    // Number of bytes to transfer
  volatile uint32_t             num_transferred;        // Number of bytes transferred in total
  volatile uint16_t             num_last_transferred;   // Number of bytes transferred in last transfer
  volatile uint16_t             num_curr_transferring;  // Number of bytes currently being transferred
} CH_Info_t;

// Instance run-time information (RW)
typedef struct {
  ARM_USBH_SignalPortEvent_t    cb_port_event;          // Port event callback
  ARM_USBH_SignalPipeEvent_t    cb_pipe_event;          // Pipe event callback
  DriverStatus_t                drv_status;             // Driver status
  volatile uint8_t              port_connected;         // Host Port connected state
  volatile uint8_t              periodic_pipes_exist;   // Periodic pipes existence
           uint8_t              max_channels;           // Maximum number of Host Channels
  CH_Info_t                     ch_info[USBH_MAX_PIPE_NUM];     // Host Controller channel information
} RW_Info_t;

// Instance compile-time information (RO)
// also contains pointer to run-time information
typedef struct {
  HCD_HandleTypeDef            *ptr_hhcd;               // Pointer to HCD handle
  RW_Info_t                    *ptr_rw_info;            // Pointer to run-time information (RW)
} RO_Info_t;

// Information definitions (for instances)
#ifdef MX_USBH0
INFO_DEFINE(0)
#endif
#ifdef MX_USBH1
INFO_DEFINE(1)
#endif

// List of available USBH instance infos
static const RO_Info_t * const usbh_ro_info_list[] = {
#ifdef MX_USBH0
  &usbh0_ro_info,
#endif
#ifdef MX_USBH1
  &usbh1_ro_info,
#endif
  NULL
};

// Local functions prototypes
static const RO_Info_t         *USBH_GetInfo                    (const HCD_HandleTypeDef * const hhcd);
static ARM_DRIVER_VERSION       USBH_GetVersion                 (void);
static ARM_USBH_CAPABILITIES    USBH_GetCapabilities            (void);
static int32_t                  USBHn_Initialize                (const RO_Info_t * const ptr_ro_info, ARM_USBH_SignalPortEvent_t cb_port_event, ARM_USBH_SignalPipeEvent_t cb_pipe_event);
static int32_t                  USBHn_Uninitialize              (const RO_Info_t * const ptr_ro_info);
static int32_t                  USBHn_PowerControl              (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state);
static int32_t                  USBHn_PortVbusOnOff             (const RO_Info_t * const ptr_ro_info, uint8_t port, bool vbus);
static int32_t                  USBHn_PortReset                 (const RO_Info_t * const ptr_ro_info, uint8_t port);
static int32_t                  USBHn_PortSuspend               (const RO_Info_t * const ptr_ro_info, uint8_t port);
static int32_t                  USBHn_PortResume                (const RO_Info_t * const ptr_ro_info, uint8_t port);
static ARM_USBH_PORT_STATE      USBHn_PortGetState              (const RO_Info_t * const ptr_ro_info, uint8_t port);
static ARM_USBH_PIPE_HANDLE     USBHn_PipeCreate                (const RO_Info_t * const ptr_ro_info, uint8_t dev_addr, uint8_t dev_speed, uint8_t hub_addr, uint8_t hub_port, uint8_t ep_addr, uint8_t ep_type, uint16_t ep_max_packet_size, uint8_t ep_interval);
static int32_t                  USBHn_PipeModify                (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl, uint8_t dev_addr, uint8_t dev_speed, uint8_t hub_addr, uint8_t hub_port, uint16_t ep_max_packet_size);
static int32_t                  USBHn_PipeDelete                (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl);
static int32_t                  USBHn_PipeReset                 (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl);
static int32_t                  USBHn_PipeTransfer              (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl, uint32_t packet, uint8_t *data, uint32_t num);
static uint32_t                 USBHn_PipeTransferGetResult     (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl);
static int32_t                  USBHn_PipeTransferAbort         (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl);
static uint16_t                 USBHn_GetFrameNumber            (const RO_Info_t * const ptr_ro_info);

// Local driver functions declarations (for instances)
#ifdef MX_USBH0
FUNCS_DECLARE(0)
#endif
#ifdef MX_USBH1
FUNCS_DECLARE(1)
#endif

// Auxiliary functions

/**
  \fn          RO_Info_t *USBH_GetInfo (const HCD_HandleTypeDef * const hhcd)
  \brief       Get pointer to RO_Info_t structure corresponding to specified hhcd.
  \param[in]   hhcd     Pointer to USBH handle structure (HCD_HandleTypeDef)
  \return      pointer to USBH RO info structure (RO_Info_t)
*/
static const RO_Info_t *USBH_GetInfo (const HCD_HandleTypeDef * const hhcd) {
  const RO_Info_t *ptr_ro_info;
        uint8_t    i;

  ptr_ro_info = NULL;
  i            = 0U;

  // Find USBH which uses same hhcd handle as parameter hhcd
  for (i = 0U; i < (sizeof(usbh_ro_info_list)/sizeof(RO_Info_t *)); i++) {
    if (usbh_ro_info_list[i] != NULL) {
      if (usbh_ro_info_list[i]->ptr_hhcd == hhcd) {
        ptr_ro_info = usbh_ro_info_list[i];
        break;
      }
    }
  }

  return ptr_ro_info;
}

/**
  \fn          void USBH_Transfer_UpdateInfo (const HCD_HandleTypeDef * const hhcd, uint8_t ch)
  \brief       Update transfer information.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ch             Host Channel used for transfer.
*/
__STATIC_INLINE void USBH_Transfer_UpdateInfo (const RO_Info_t * const ptr_ro_info, uint8_t ch) {
  uint32_t num_last_transferred;
  uint32_t xfer_count;

  xfer_count = HAL_HCD_HC_GetXferCount(ptr_ro_info->ptr_hhcd, ch);
  if (ptr_ro_info->ptr_rw_info->ch_info[ch].hal_direction == 0U) {
    // For OUT transfer, xfer_count is number of bytes left to transfer, so
    // after transfer successfully finishes it contains value 0.
    num_last_transferred = ptr_ro_info->ptr_hhcd->hc[ch].xfer_len - xfer_count;
  } else {
    num_last_transferred = xfer_count;
  }

  ptr_ro_info->ptr_rw_info->ch_info[ch].num_transferred      += num_last_transferred;
  ptr_ro_info->ptr_rw_info->ch_info[ch].num_last_transferred  = num_last_transferred;
  ptr_ro_info->ptr_rw_info->ch_info[ch].num_curr_transferring = 0U;
}

/**
  \fn          int32_t USBH_Transfer_GetStatus (const HCD_HandleTypeDef * const hhcd, uint8_t ch)
  \brief       Get transfer status.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ch             Host Channel used for transfer.
  \return      0 if transfer is finished, -1 if transfer is not finished.
*/
__STATIC_INLINE int32_t USBH_Transfer_GetStatus (const RO_Info_t * const ptr_ro_info, uint8_t ch) {

  if ((ptr_ro_info->ptr_rw_info->ch_info[ch].num == ptr_ro_info->ptr_rw_info->ch_info[ch].num_transferred) ||
      (ptr_ro_info->ptr_rw_info->ch_info[ch].num_last_transferred != ptr_ro_info->ptr_rw_info->ch_info[ch].ep_max_packet_size)) {
    // If short packet or ZLP were received in IN direction, or if less data then requested was
    // sent in OUT direction then transfer is considered finished
    ptr_ro_info->ptr_rw_info->ch_info[ch].transfer_registered = 0U;
    return 0;
  }

  return -1;
}

/**
  \fn          int32_t USBH_Transfer_Packet (const HCD_HandleTypeDef * const hhcd, uint8_t ch)
  \brief       Transfer (next) packet from multi-packet transfer or just complete packet for single-packet transfer.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ch             Host Channel used for transfer.
  \return      0 if transfer started successfully, -1 transfer start failed.
*/
__STATIC_INLINE int32_t USBH_Transfer_Packet (const RO_Info_t * const ptr_ro_info, uint8_t ch) {
  uint32_t num_to_transfer;

  if (ptr_ro_info->ptr_rw_info->ch_info[ch].transfer_registered == 0U) {
    // No request was registered
    return -1;
  }

  num_to_transfer = 0U;
  if (ptr_ro_info->ptr_rw_info->ch_info[ch].num > ptr_ro_info->ptr_rw_info->ch_info[ch].num_transferred) {
    num_to_transfer = ptr_ro_info->ptr_rw_info->ch_info[ch].num - ptr_ro_info->ptr_rw_info->ch_info[ch].num_transferred;
  }
  if (ptr_ro_info->ptr_rw_info->ch_info[ch].ep_type != ARM_USB_ENDPOINT_CONTROL) {
    // For all other endpoint types except Control, split transfer into packets
    // For Control leave transfer as requested because HAL has problem handling toggle bits on Control transfers
    if (num_to_transfer > ptr_ro_info->ptr_rw_info->ch_info[ch].ep_max_packet_size) {
      num_to_transfer = ptr_ro_info->ptr_rw_info->ch_info[ch].ep_max_packet_size;
    }
  }

  ptr_ro_info->ptr_rw_info->ch_info[ch].num_curr_transferring = num_to_transfer;
  if (HAL_HCD_HC_SubmitRequest(ptr_ro_info->ptr_hhcd,
                               ch,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].hal_direction,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].ep_type,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].hal_token,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].data + ptr_ro_info->ptr_rw_info->ch_info[ch].num_transferred,
                               num_to_transfer,
                               0U) != HAL_OK) {
    // If request submit has failed
    ptr_ro_info->ptr_rw_info->ch_info[ch].num_curr_transferring = 0U;
    return -1;
  }

  return 0;
}

/**
  \fn          int32_t USBH_Transfer_RepeatPacket (const HCD_HandleTypeDef * const hhcd, uint8_t ch)
  \brief       Repeat last packet transfer.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   ch             Host Channel used for transfer.
  \return      0 if transfer started successfully, -1 transfer start failed.
*/
__STATIC_INLINE int32_t USBH_Transfer_RepeatPacket (const RO_Info_t * const ptr_ro_info, uint8_t ch) {

  if (HAL_HCD_HC_SubmitRequest(ptr_ro_info->ptr_hhcd,
                               ch,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].hal_direction,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].ep_type,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].hal_token,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].data + ptr_ro_info->ptr_rw_info->ch_info[ch].num_transferred,
                               ptr_ro_info->ptr_rw_info->ch_info[ch].num_curr_transferring,
                               0U) != HAL_OK) {
    // If request submit has failed
    ptr_ro_info->ptr_rw_info->ch_info[ch].num_curr_transferring = 0U;
    return -1;
  }

  return 0;
}

// Hardware-specific functions *************************************************

// This function should be provided by user code if hardware-specific driving
// for VBUS is necessary, thus override the default implementation below.

/**
  \fn          int32_t USBH_HW_VbusOnOff (HCD_HandleTypeDef *ptr_hhcd, bool vbus)
  \brief       Hardware-specific control of VBUS line.
  \param[in]   ptr_hhcd     Pointer to hhcd
  \param[in]   vbus
                - \b false VBUS off
                - \b true  VBUS on
  \return      0 on success, -1 on error.
*/
__WEAK int32_t USBH_HW_VbusOnOff (HCD_HandleTypeDef *ptr_hhcd, bool vbus) {
  (void)ptr_hhcd;
  (void)vbus;

  return 0;
}

// Driver functions ************************************************************

/**
  \fn          ARM_DRIVER_VERSION USBH_GetVersion (void)
  \brief       Get driver version.
  \return      \ref ARM_DRIVER_VERSION
*/
static ARM_DRIVER_VERSION USBH_GetVersion (void) {
  return driver_version;
}

/**
  \fn          ARM_USBH_CAPABILITIES USBH_GetCapabilities (void)
  \brief       Get driver capabilities.
  \return      \ref ARM_USBH_CAPABILITIES
*/
static ARM_USBH_CAPABILITIES USBH_GetCapabilities (void) {
  return driver_capabilities;
}

/**
  \fn          int32_t USBHn_Initialize (const RO_Info_t * const    ptr_ro_info,
                                         ARM_USBH_SignalPortEvent_t cb_port_event,
                                         ARM_USBH_SignalPipeEvent_t cb_pipe_event)
  \brief       Initialize USB Host Interface.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   cb_port_event  Pointer to \ref ARM_USBH_SignalPortEvent
  \param[in]   cb_pipe_event  Pointer to \ref ARM_USBH_SignalPipeEvent
  \return      \ref execution_status
*/
static int32_t USBHn_Initialize (const RO_Info_t * const    ptr_ro_info,
                                 ARM_USBH_SignalPortEvent_t cb_port_event,
                                 ARM_USBH_SignalPipeEvent_t cb_pipe_event) {
  uint8_t max_ch;

  // Clear run-time info
  memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

  // Calculate maximum number of available Host Channels
  max_ch = USBH_MAX_PIPE_NUM;
  if (max_ch > ptr_ro_info->ptr_hhcd->Init.Host_channels) {
    max_ch = ptr_ro_info->ptr_hhcd->Init.Host_channels;
  }
  ptr_ro_info->ptr_rw_info->max_channels = max_ch;

  // Register callback functions
  ptr_ro_info->ptr_rw_info->cb_port_event = cb_port_event;
  ptr_ro_info->ptr_rw_info->cb_pipe_event = cb_pipe_event;

  // Set driver status to initialized
  ptr_ro_info->ptr_rw_info->drv_status.initialized = 1U;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBHn_Uninitialize (const RO_Info_t * const ptr_ro_info)
  \brief       De-initialize USB Host Interface.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \return      \ref execution_status
*/
static int32_t USBHn_Uninitialize (const RO_Info_t * const ptr_ro_info) {

  if (ptr_ro_info->ptr_rw_info->drv_status.powered != 0U) {
    // If peripheral is powered, power off the peripheral
    (void)USBHn_PowerControl(ptr_ro_info, ARM_POWER_OFF);
  }

  // Clear run-time info
  memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBHn_PowerControl (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state)
  \brief       Control USB Host Interface Power.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   state  Power state
  \return      \ref execution_status
*/
static int32_t USBHn_PowerControl (const RO_Info_t * const ptr_ro_info, ARM_POWER_STATE state) {
  ARM_USBH_SignalPortEvent_t cb_port_event;
  ARM_USBH_SignalPipeEvent_t cb_pipe_event;
  DriverStatus_t             drv_status;
  uint8_t                    max_ch;

  switch (state) {
    case ARM_POWER_FULL:
      if (ptr_ro_info->ptr_rw_info->drv_status.initialized == 0U) {
        return ARM_DRIVER_ERROR;
      }

      // Store variables we need to preserve
      cb_port_event = ptr_ro_info->ptr_rw_info->cb_port_event;
      cb_pipe_event = ptr_ro_info->ptr_rw_info->cb_pipe_event;
      drv_status    = ptr_ro_info->ptr_rw_info->drv_status;
      max_ch        = ptr_ro_info->ptr_rw_info->max_channels;

      // Clear run-time info
      memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

      // Restore variables we wanted to preserve
      ptr_ro_info->ptr_rw_info->cb_port_event = cb_port_event;
      ptr_ro_info->ptr_rw_info->cb_pipe_event = cb_pipe_event;
      ptr_ro_info->ptr_rw_info->drv_status    = drv_status;
      ptr_ro_info->ptr_rw_info->max_channels  = max_ch;

      // Initialize pins, clocks, interrupts and peripheral
      if (HAL_HCD_Init(ptr_ro_info->ptr_hhcd) != HAL_OK) {
        return ARM_DRIVER_ERROR;
      }

      // Set driver status to powered
      ptr_ro_info->ptr_rw_info->drv_status.powered = 1U;
      break;

    case ARM_POWER_OFF:

      // Stop USB Host operation
      (void)HAL_HCD_Stop(ptr_ro_info->ptr_hhcd);

      // De-initialize pins, clocks, interrupts and peripheral
      (void)HAL_HCD_DeInit(ptr_ro_info->ptr_hhcd);

      // Set driver status to not powered
      ptr_ro_info->ptr_rw_info->drv_status.powered = 0U;

      // Store variables we need to preserve
      cb_port_event = ptr_ro_info->ptr_rw_info->cb_port_event;
      cb_pipe_event = ptr_ro_info->ptr_rw_info->cb_pipe_event;
      drv_status    = ptr_ro_info->ptr_rw_info->drv_status;
      max_ch        = ptr_ro_info->ptr_rw_info->max_channels;

      // Clear run-time info
      memset((void *)ptr_ro_info->ptr_rw_info, 0, sizeof(RW_Info_t));

      // Restore variables we wanted to preserve
      ptr_ro_info->ptr_rw_info->cb_port_event = cb_port_event;
      ptr_ro_info->ptr_rw_info->cb_pipe_event = cb_pipe_event;
      ptr_ro_info->ptr_rw_info->drv_status    = drv_status;
      ptr_ro_info->ptr_rw_info->max_channels  = max_ch;
      break;

    case ARM_POWER_LOW:
      return ARM_DRIVER_ERROR_UNSUPPORTED;

    default:
      return ARM_DRIVER_ERROR_PARAMETER;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBHn_PortVbusOnOff (const RO_Info_t * const ptr_ro_info, uint8_t port, bool vbus)
  \brief       Root HUB Port VBUS on/off.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   port  Root HUB Port Number
  \param[in]   vbus
                - \b false VBUS off
                - \b true  VBUS on
  \return      \ref execution_status
*/
static int32_t USBHn_PortVbusOnOff (const RO_Info_t * const ptr_ro_info, uint8_t port, bool vbus) {

  (void)port;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (vbus == true) {
    // Start USB Host operation
    if (HAL_HCD_Start(ptr_ro_info->ptr_hhcd) != HAL_OK) {
      return ARM_DRIVER_ERROR;
    }
  } else {
    if (USB_DriveVbus(ptr_ro_info->ptr_hhcd->Instance, 0U) != HAL_OK) {
      return ARM_DRIVER_ERROR;
    }
  }

  // Allow hardware-specific VBUS control
  if (USBH_HW_VbusOnOff(ptr_ro_info->ptr_hhcd, vbus) != 0) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBHn_PortReset (const RO_Info_t * const ptr_ro_info, uint8_t port)
  \brief       Do Root HUB Port Reset.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   port  Root HUB Port Number
  \return      \ref execution_status
*/
static int32_t USBHn_PortReset (const RO_Info_t * const ptr_ro_info, uint8_t port) {

  (void)port;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  if (HAL_HCD_ResetPort(ptr_ro_info->ptr_hhcd) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBHn_PortSuspend (const RO_Info_t * const ptr_ro_info, uint8_t port)
  \brief       Suspend Root HUB Port (stop generating SOFs).
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   port  Root HUB Port Number
  \return      \ref execution_status
*/
static int32_t USBHn_PortSuspend (const RO_Info_t * const ptr_ro_info, uint8_t port) {
  (void)ptr_ro_info;
  (void)port;

  return ARM_DRIVER_ERROR_UNSUPPORTED;
}

/**
  \fn          int32_t USBHn_PortResume (const RO_Info_t * const ptr_ro_info, uint8_t port)
  \brief       Resume Root HUB Port (start generating SOFs).
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   port  Root HUB Port Number
  \return      \ref execution_status
*/
static int32_t USBHn_PortResume (const RO_Info_t * const ptr_ro_info, uint8_t port) {
  (void)ptr_ro_info;
  (void)port;

  return ARM_DRIVER_ERROR_UNSUPPORTED;
}

/**
  \fn          ARM_USBH_PORT_STATE USBHn_PortGetState (const RO_Info_t * const ptr_ro_info, uint8_t port)
  \brief       Get current Root HUB Port State.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   port  Root HUB Port Number
  \return      Port State \ref ARM_USBH_PORT_STATE
*/
static ARM_USBH_PORT_STATE USBHn_PortGetState (const RO_Info_t * const ptr_ro_info, uint8_t port) {
  ARM_USBH_PORT_STATE port_state;

  (void)port;

  // Clear state structure
  memset(&port_state, 0, sizeof(ARM_USBH_PORT_STATE));

  // Process additionally handled communication information
  if (ptr_ro_info->ptr_rw_info->port_connected != 0U) {
    port_state.connected = 1U;
  }
  switch (HAL_HCD_GetCurrentSpeed(ptr_ro_info->ptr_hhcd)) {
    case HCD_SPEED_FULL:
      port_state.speed = ARM_USB_SPEED_FULL;
      break;
    case HCD_SPEED_HIGH:
      port_state.speed = ARM_USB_SPEED_HIGH;
      break;
    default:
      break;
  }

  // Over-current cannot be detected with HAL (no callback)

  return port_state;
}

/**
  \fn          ARM_USBH_PIPE_HANDLE USBHn_PipeCreate (const RO_Info_t * const ptr_ro_info,
                                                            uint8_t           dev_addr,
                                                            uint8_t           dev_speed,
                                                            uint8_t           hub_addr,
                                                            uint8_t           hub_port,
                                                            uint8_t           ep_addr,
                                                            uint8_t           ep_type,
                                                            uint16_t          ep_max_packet_size,
                                                            uint8_t           ep_interval)
  \brief       Create Pipe in System.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   dev_addr   Device Address
  \param[in]   dev_speed  Device Speed
  \param[in]   hub_addr   Hub Address
  \param[in]   hub_port   Hub Port
  \param[in]   ep_addr    Endpoint Address
                - ep_addr.0..3: Address
                - ep_addr.7:    Direction
  \param[in]   ep_type    Endpoint Type (ARM_USB_ENDPOINT_xxx)
  \param[in]   ep_max_packet_size Endpoint Maximum Packet Size
  \param[in]   ep_interval        Endpoint Polling Interval
  \return      Pipe Handle \ref ARM_USBH_PIPE_HANDLE
*/
static ARM_USBH_PIPE_HANDLE USBHn_PipeCreate (const RO_Info_t * const ptr_ro_info,
                                                    uint8_t           dev_addr,
                                                    uint8_t           dev_speed,
                                                    uint8_t           hub_addr,
                                                    uint8_t           hub_port,
                                                    uint8_t           ep_addr,
                                                    uint8_t           ep_type,
                                                    uint16_t          ep_max_packet_size,
                                                    uint8_t           ep_interval) {
  uint8_t ch;
  uint8_t dev_speed_hal;

  (void)hub_addr;
  (void)hub_port;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  // Find free channel
  ch = 0xFFU;
  for (uint8_t i = 0U; i < ptr_ro_info->ptr_rw_info->max_channels; i++) {
    if (ptr_ro_info->ptr_rw_info->ch_info[i].ch_used == 0U) {
      ch = i;
      break;
    }
  }

  if (ch == 0xFFU) {                    // If no free channel(s) available
    return ARM_DRIVER_ERROR;
  }

  dev_speed_hal = HCD_DEVICE_SPEED_FULL;
  switch (dev_speed) {
    case ARM_USB_SPEED_LOW:
      dev_speed_hal = HCD_DEVICE_SPEED_LOW;
      break;
    case ARM_USB_SPEED_FULL:
      dev_speed_hal = HCD_DEVICE_SPEED_FULL;
      break;
    case ARM_USB_SPEED_HIGH:
      dev_speed_hal = HCD_DEVICE_SPEED_HIGH;
      break;
    default:
      break;
  }

  // Initialize channel used for new pipe
  if (HAL_HCD_HC_Init(ptr_ro_info->ptr_hhcd, ch, ep_addr, dev_addr, dev_speed_hal, ep_type, ep_max_packet_size) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  // Reset internal structure toggle values because HAL does not support resetting toggle bits
  if (ptr_ro_info->ptr_hhcd->hc[ch].ep_is_in != 0U) {
    ptr_ro_info->ptr_hhcd->hc[ch].toggle_in  = 0U;
  } else {
    ptr_ro_info->ptr_hhcd->hc[ch].toggle_out = 0U;
  }

  // Update channel info
  ptr_ro_info->ptr_rw_info->ch_info[ch].ep_addr            = ep_addr;
  ptr_ro_info->ptr_rw_info->ch_info[ch].ep_type            = ep_type;
  ptr_ro_info->ptr_rw_info->ch_info[ch].ep_max_packet_size = ep_max_packet_size;
  ptr_ro_info->ptr_rw_info->ch_info[ch].ch_used            = 1U;

  // For Interrupt and Isochronous endpoints store interval and reset cd_time
  switch (ep_type) {
    case ARM_USB_ENDPOINT_CONTROL:
    case ARM_USB_ENDPOINT_BULK:
      break;
    case ARM_USB_ENDPOINT_ISOCHRONOUS:
    case ARM_USB_ENDPOINT_INTERRUPT:
      // Periodic pipes exist
      ptr_ro_info->ptr_rw_info->periodic_pipes_exist = 1U;

      // Mark pipe as periodic pipe
      ptr_ro_info->ptr_rw_info->ch_info[ch].periodic = 1U;

      if (dev_speed == ARM_USB_SPEED_HIGH) {
        if ((ep_interval > 0U) && (ep_interval <= 16U)) {
          ptr_ro_info->ptr_rw_info->ch_info[ch].interval = (uint16_t)(1U << (ep_interval - 1U));
        }
      } else if ((dev_speed == ARM_USB_SPEED_FULL) || (dev_speed == ARM_USB_SPEED_LOW)) {
        if (ep_interval > 0U) {
          ptr_ro_info->ptr_rw_info->ch_info[ch].interval = ep_interval;
        }
      }
      ptr_ro_info->ptr_rw_info->ch_info[ch].cd_time = 0U;
      break;
  }

  return (ch | PIPE_HANDLE_FLAG);
}

/**
  \fn          int32_t USBHn_PipeModify (const RO_Info_t * const    ptr_ro_info,
                                               ARM_USBH_PIPE_HANDLE pipe_hndl,
                                               uint8_t              dev_addr,
                                               uint8_t              dev_speed,
                                               uint8_t              hub_addr,
                                               uint8_t              hub_port,
                                               uint16_t             ep_max_packet_size)
  \brief       Modify Pipe in System.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   pipe_hndl  Pipe Handle
  \param[in]   dev_addr   Device Address
  \param[in]   dev_speed  Device Speed
  \param[in]   hub_addr   Hub Address
  \param[in]   hub_port   Hub Port
  \param[in]   ep_max_packet_size Endpoint Maximum Packet Size
  \return      \ref execution_status
*/
static int32_t USBHn_PipeModify (const RO_Info_t * const    ptr_ro_info,
                                       ARM_USBH_PIPE_HANDLE pipe_hndl,
                                       uint8_t              dev_addr,
                                       uint8_t              dev_speed,
                                       uint8_t              hub_addr,
                                       uint8_t              hub_port,
                                       uint16_t             ep_max_packet_size) {

  uint8_t ch;
  uint8_t ep_addr;
  uint8_t ep_type;
  uint8_t dev_speed_hal;

  (void)hub_addr;
  (void)hub_port;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  ch = (uint8_t)(pipe_hndl & ~PIPE_HANDLE_FLAG);

  if (ch >= ptr_ro_info->ptr_rw_info->max_channels) {
    return ARM_DRIVER_ERROR;
  }

  if (ptr_ro_info->ptr_rw_info->ch_info[ch].ch_used == 0U) {
    // If channel was not yet configured
    return ARM_DRIVER_ERROR;
  }

  ep_addr = ptr_ro_info->ptr_rw_info->ch_info[ch].ep_addr;

  dev_speed_hal = HCD_DEVICE_SPEED_FULL;
  switch (dev_speed) {
    case ARM_USB_SPEED_LOW:
      dev_speed_hal = HCD_DEVICE_SPEED_LOW;
      break;
    case ARM_USB_SPEED_FULL:
      dev_speed_hal = HCD_DEVICE_SPEED_FULL;
      break;
    case ARM_USB_SPEED_HIGH:
      dev_speed_hal = HCD_DEVICE_SPEED_HIGH;
      break;
    default:
      break;
  }

  ep_type = ptr_ro_info->ptr_rw_info->ch_info[ch].ep_type;

  // Re-initialize channel with new parameters
  if (HAL_HCD_HC_Init(ptr_ro_info->ptr_hhcd, ch, ep_addr, dev_addr, dev_speed_hal, ep_type, ep_max_packet_size) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  // Update channel info
  ptr_ro_info->ptr_rw_info->ch_info[ch].ep_max_packet_size = ep_max_packet_size;

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBHn_PipeDelete (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl)
  \brief       Delete Pipe from System.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   pipe_hndl  Pipe Handle
  \return      \ref execution_status
*/
static int32_t USBHn_PipeDelete (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl) {
  uint8_t ch;
  uint8_t periodic;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  ch = (uint8_t)(pipe_hndl & ~PIPE_HANDLE_FLAG);

  if (ch >= ptr_ro_info->ptr_rw_info->max_channels) {
    return ARM_DRIVER_ERROR;
  }

  if (ptr_ro_info->ptr_rw_info->ch_info[ch].ch_used == 0U) {
    // If channel was not yet configured, consider deletion successful
    return ARM_DRIVER_OK;
  }

  // Halt channel as closest thing to deletion
  if (HAL_HCD_HC_Halt(ptr_ro_info->ptr_hhcd, ch) != HAL_OK) {
    return ARM_DRIVER_ERROR;
  }

  // Clear Host Channel information
  memset(&ptr_ro_info->ptr_rw_info->ch_info[ch], 0, sizeof(CH_Info_t));

  periodic = 0U;
  for (uint8_t i = 0U; i < ptr_ro_info->ptr_rw_info->max_channels; i++) {
    if (ptr_ro_info->ptr_rw_info->ch_info[i].periodic != 0U) {
      periodic = 1U;
    }
  }
  if (periodic == 0U) {
    // If no periodic pipes exist
    ptr_ro_info->ptr_rw_info->periodic_pipes_exist = 0U;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBHn_PipeReset (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl)
  \brief       Reset Pipe.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   pipe_hndl  Pipe Handle
  \return      \ref execution_status
*/
static int32_t USBHn_PipeReset (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl) {
  uint8_t ch;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  ch = (uint8_t)(pipe_hndl & ~PIPE_HANDLE_FLAG);

  if (ch >= ptr_ro_info->ptr_rw_info->max_channels) {
    return ARM_DRIVER_ERROR;
  }

  if (ptr_ro_info->ptr_rw_info->ch_info[ch].ch_used == 0U) {
    // If channel was not yet configured
    return ARM_DRIVER_ERROR;
  }

  // Change internal structure toggle values because HAL does not support resetting toggle bits
  if (ptr_ro_info->ptr_hhcd->hc[ch].ep_is_in != 0U) {
    ptr_ro_info->ptr_hhcd->hc[ch].toggle_in  = 0U;
  } else {
    ptr_ro_info->ptr_hhcd->hc[ch].toggle_out = 0U;
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          int32_t USBHn_PipeTransfer (const RO_Info_t * const    ptr_ro_info,
                                                 ARM_USBH_PIPE_HANDLE pipe_hndl,
                                                 uint32_t             packet,
                                                 uint8_t             *data,
                                                 uint32_t             num)
  \brief       Transfer packets through USB Pipe.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   pipe_hndl  Pipe Handle
  \param[in]   packet     Packet information
  \param[in]   data       Pointer to buffer with data to send or for data to receive
  \param[in]   num        Number of data bytes to transfer
  \return      \ref execution_status
*/
static int32_t USBHn_PipeTransfer (const RO_Info_t * const    ptr_ro_info,
                                         ARM_USBH_PIPE_HANDLE pipe_hndl,
                                         uint32_t             packet,
                                         uint8_t             *data,
                                         uint32_t             num) {

  uint8_t  ch;
  uint8_t  dir;
  uint8_t  ep_type;
  uint8_t  token;
  uint8_t  do_transfer;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return ARM_DRIVER_ERROR;
  }

  ch = (uint8_t)(pipe_hndl & ~PIPE_HANDLE_FLAG);

  if (ch >= ptr_ro_info->ptr_rw_info->max_channels) {
    return ARM_DRIVER_ERROR;
  }

  switch (packet & ARM_USBH_PACKET_TOKEN_Msk) {
    case ARM_USBH_PACKET_SETUP:
      dir     = 0U;
      token   = 0U;
      break;
    case ARM_USBH_PACKET_OUT:
      dir     = 0U;
      token   = 1U;
      break;
    case ARM_USBH_PACKET_IN:
      dir     = 1U;
      token   = 1U;
      break;
    case ARM_USBH_PACKET_PING:
      dir     = 0U;
      token   = 1U;
      break;
    default:
      return ARM_DRIVER_ERROR_PARAMETER;
  }
  ep_type = ptr_ro_info->ptr_rw_info->ch_info[ch].ep_type;

  // Clear do_ping value as it gets automatically set on OUT transfer on reception of NYET packet
  // In general, when NYET is received the pipe should report ARM_USBH_EVENT_HANDSHAKE_NYET, however
  // HAL does not generate special event on NYET reception but it only sets do_ping to 1.
  ptr_ro_info->ptr_hhcd->hc[ch].do_ping = 0U;

  // Register transfer request
  ptr_ro_info->ptr_rw_info->ch_info[ch].hal_direction         = dir;
  ptr_ro_info->ptr_rw_info->ch_info[ch].hal_token             = token;
  ptr_ro_info->ptr_rw_info->ch_info[ch].data                  = data;
  ptr_ro_info->ptr_rw_info->ch_info[ch].num                   = num;
  ptr_ro_info->ptr_rw_info->ch_info[ch].num_transferred       = 0U;
  ptr_ro_info->ptr_rw_info->ch_info[ch].num_curr_transferring = 0U;
  ptr_ro_info->ptr_rw_info->ch_info[ch].num_last_transferred  = 0U;

  do_transfer = 1U;
  if ((ep_type == ARM_USB_ENDPOINT_INTERRUPT) || (ep_type == ARM_USB_ENDPOINT_INTERRUPT)) {
    // For Interrupt and Isochronous transfers
    if (ptr_ro_info->ptr_rw_info->ch_info[ch].cd_time != 0U) {
      // If countdown time (cd_time) is active then just register transfer request, but transfer
      // will be started when cd_time variable counts down to 0 from HAL_HCD_SOF_Callback
      do_transfer = 0U;
    } else {
      // If countdown time (cd_time) is not active then activate the transfer and load countdown cd_time variable
      // for next transfer
      ptr_ro_info->ptr_rw_info->ch_info[ch].cd_time = ptr_ro_info->ptr_rw_info->ch_info[ch].interval;
    }
  }

  // Set transfer registered flag
  ptr_ro_info->ptr_rw_info->ch_info[ch].transfer_registered = 1U;

  if (do_transfer != 0U) {
    if (USBH_Transfer_Packet(ptr_ro_info, ch) != 0) {
      return ARM_DRIVER_ERROR;
    }
  }

  return ARM_DRIVER_OK;
}

/**
  \fn          uint32_t USBHn_PipeTransferGetResult (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl)
  \brief       Get result of USB Pipe transfer.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   pipe_hndl  Pipe Handle
  \return      number of successfully transferred data bytes
*/
static uint32_t USBHn_PipeTransferGetResult (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl) {
  uint8_t ch;

  if (ptr_ro_info->ptr_rw_info->drv_status.powered == 0U) {
    return 0U;
  }

  ch = (uint8_t)(pipe_hndl & ~PIPE_HANDLE_FLAG);

  if (ch >= ptr_ro_info->ptr_rw_info->max_channels) {
    return 0U;
  }

  return (ptr_ro_info->ptr_rw_info->ch_info[ch].num_transferred);
}

/**
  \fn          int32_t USBHn_PipeTransferAbort (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl)
  \brief       Abort current USB Pipe transfer.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \param[in]   pipe_hndl  Pipe Handle
  \return      \ref execution_status
*/
static int32_t USBHn_PipeTransferAbort (const RO_Info_t * const ptr_ro_info, ARM_USBH_PIPE_HANDLE pipe_hndl) {
  (void)ptr_ro_info;
  (void)pipe_hndl;

  // This function does nothing as there is no support in HAL for aborting active transfers.
  // We return ARM_DRIVER_OK so higher level software can operate normally.

  return ARM_DRIVER_OK;
}

/**
  \fn          uint16_t USBHn_GetFrameNumber (const RO_Info_t * const ptr_ro_info)
  \brief       Get current USB Frame Number.
  \param[in]   ptr_ro_info    Pointer to USBD RO info structure (RO_Info_t)
  \return      Frame Number
*/
static uint16_t USBHn_GetFrameNumber (const RO_Info_t * const ptr_ro_info) {
  return (HAL_HCD_GetCurrentFrame(ptr_ro_info->ptr_hhcd));
}

// HAL callback functions ******************************************************

/**
  \fn          void HAL_HCD_SOF_Callback (HCD_HandleTypeDef *hhcd)
  \brief       SOF callback.
  \param[in]   hhcd     HCD handle
  */
void HAL_HCD_SOF_Callback (HCD_HandleTypeDef *hhcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBH_GetInfo(hhcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  if (ptr_ro_info->ptr_rw_info->periodic_pipes_exist != 0U) {
    // If periodic pipes exist, handle interval here.
    // For interrupt and isochronous transfers countdown time (cd_time) is handled here.
    // If cd_time variable counts down to 0 and if transfer request was registered
    // we start the transfer from here.
    for (uint8_t i = 0U; i < ptr_ro_info->ptr_rw_info->max_channels; i++) {
      if (ptr_ro_info->ptr_rw_info->ch_info[i].periodic != 0U) {
        // If the pipe is periodic

        if (ptr_ro_info->ptr_rw_info->ch_info[i].cd_time != 0U) {
          ptr_ro_info->ptr_rw_info->ch_info[i].cd_time--;
          if (ptr_ro_info->ptr_rw_info->ch_info[i].cd_time == 0U) {
            // If interval has expired
            ptr_ro_info->ptr_rw_info->ch_info[i].cd_time = ptr_ro_info->ptr_rw_info->ch_info[i].interval;

            // Start the transfer
            (void)USBH_Transfer_Packet(ptr_ro_info, i);
          }
        }
      }
    }
  }
}

/**
  \fn          void HAL_HCD_Connect_Callback (HCD_HandleTypeDef *hhcd)
  \brief       Connection Event callback.
  \param[in]   hhcd     HCD handle
  */
void HAL_HCD_Connect_Callback (HCD_HandleTypeDef *hhcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBH_GetInfo(hhcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->port_connected = 1U;

  if (ptr_ro_info->ptr_rw_info->cb_port_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_port_event(0U, ARM_USBH_EVENT_CONNECT);
  }
}

/**
  \fn          void HAL_HCD_Disconnect_Callback (HCD_HandleTypeDef *hhcd)
  \brief       Disconnection Event callback.
  \param[in]   hhcd     HCD handle
  */
void HAL_HCD_Disconnect_Callback (HCD_HandleTypeDef *hhcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBH_GetInfo(hhcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  ptr_ro_info->ptr_rw_info->port_connected = 0U;

  if (ptr_ro_info->ptr_rw_info->cb_port_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_port_event(0U, ARM_USBH_EVENT_DISCONNECT);
  }
}

/**
  \fn          void HAL_HCD_PortEnabled_Callback (HCD_HandleTypeDef *hhcd)
  \brief       Port Enabled Event callback.
  \param[in]   hhcd     HCD handle
  */
void HAL_HCD_PortEnabled_Callback (HCD_HandleTypeDef *hhcd) {
  const RO_Info_t *ptr_ro_info;

  ptr_ro_info = USBH_GetInfo(hhcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  if (ptr_ro_info->ptr_rw_info->cb_port_event != NULL) {
    ptr_ro_info->ptr_rw_info->cb_port_event(0U, ARM_USBH_EVENT_RESET);
  }
}

/**
  \fn          void HAL_HCD_PortDisabled_Callback (HCD_HandleTypeDef *hhcd)
  \brief       Port Disabled Event callback.
  \param[in]   hhcd     HCD handle
  */
void HAL_HCD_PortDisabled_Callback (HCD_HandleTypeDef *hhcd) {
  (void)hhcd;
}

/**
  \fn          void HAL_HCD_HC_NotifyURBChange_Callback (HCD_HandleTypeDef *hhcd, uint8_t chnum, HCD_URBStateTypeDef urb_state)
  \brief       Notify URB state change callback.
  \param[in]   hhcd     HCD handle
  \param[in]   chnum    Channel number. This parameter can be a value from 1 to 15.
  \param[in]   urb_state:
                        This parameter can be one of these values:
                          URB_IDLE
                          URB_DONE
                          URB_NOTREADY
                          URB_NYET
                          URB_ERROR
                          URB_STALL
  */
void HAL_HCD_HC_NotifyURBChange_Callback (HCD_HandleTypeDef *hhcd, uint8_t chnum, HCD_URBStateTypeDef urb_state) {
  const RO_Info_t *ptr_ro_info;
        uint32_t   event;

  ptr_ro_info = USBH_GetInfo(hhcd);

  if (ptr_ro_info == NULL) {
    return;
  }
  if (ptr_ro_info->ptr_rw_info == NULL) {
    return;
  }

  event = 0U;
  switch (urb_state) {
    case URB_IDLE:
      break;

    case URB_DONE:
      // Here we continue sending packets until last packet of multi-packet transfer was transferred
      USBH_Transfer_UpdateInfo(ptr_ro_info, chnum);             // Update transfer information
      if (USBH_Transfer_GetStatus(ptr_ro_info, chnum) == 0) {   // If transfer is finished
        event = ARM_USBH_EVENT_TRANSFER_COMPLETE;
      } else {                                                  // If transfer is not finished
        if (USBH_Transfer_Packet(ptr_ro_info, chnum) != 0) {    // If transfer of next packet has failed
          event = ARM_USBH_EVENT_BUS_ERROR;
        }
      }
      break;

    case URB_NOTREADY:
      // For OUT transfer repeat the last packet, for IN transfer just ignore it as internally on NAK
      // the channel will be rearmed and will continue with the transfer
      if (ptr_ro_info->ptr_rw_info->ch_info[chnum].hal_direction == 0U) {
        if (USBH_Transfer_RepeatPacket(ptr_ro_info, chnum) != 0) {
          event = ARM_USBH_EVENT_BUS_ERROR;
        }
      }
      break;

    case URB_NYET:
      event = ARM_USBH_EVENT_HANDSHAKE_NYET;
      break;

    case URB_ERROR:
      event = ARM_USBH_EVENT_BUS_ERROR;
      break;

    case URB_STALL:
      event = ARM_USBH_EVENT_HANDSHAKE_STALL;
      break;

    default:
      break;
  }

  if ((ptr_ro_info->ptr_rw_info->cb_pipe_event != NULL) && (event != 0U)) {
    ptr_ro_info->ptr_rw_info->cb_pipe_event(chnum | PIPE_HANDLE_FLAG, event);
  }
}

// Local driver functions definitions (for instances)
#ifdef MX_USBH0
FUNCS_DEFINE(0)
#endif
#ifdef MX_USBH1
FUNCS_DEFINE(1)
#endif

// Global driver structures ****************************************************

#ifdef MX_USBH0
USBH_DRIVER(0)
#endif

#ifdef MX_USBH1
USBH_DRIVER(1)
#endif

#endif  // DRIVER_CONFIG_VALID

/*! \endcond */
